{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "8d2c57dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "from openai import AsyncOpenAI\n",
    "from agents import Agent, Runner, trace, function_tool, OpenAIChatCompletionsModel, input_guardrail, output_guardrail, GuardrailFunctionOutput\n",
    "from typing import Dict, List\n",
    "import sendgrid\n",
    "import os\n",
    "from sendgrid.helpers.mail import Mail, Email, To, Content\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "import ollama"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "ca42b7c5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "e4141216",
   "metadata": {},
   "outputs": [],
   "source": [
    "openai_api_key = os.getenv('OPENAI_API_KEY')\n",
    "google_api_key = os.getenv('GOOGLE_API_KEY')\n",
    "deepseek_api_key = os.getenv('DEEPSEEK_API_KEY')\n",
    "anthropic_api_key = os.getenv('ANTHROPIC_API_KEY')\n",
    "ollama_api_key = \"http://localhost:11434/api/chat\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d7168671",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OpenAI API Key exists and begins sk-proj-\n",
      "Google API Key exists and begins AI\n",
      "DeepSeek API Key exists and begins sk-\n",
      "Anthropic API Key exists and begins sk-a\n"
     ]
    }
   ],
   "source": [
    "if openai_api_key:\n",
    "    print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n",
    "else:\n",
    "    print(\"OpenAI API Key not set\")\n",
    "\n",
    "if google_api_key:\n",
    "    print(f\"Google API Key exists and begins {google_api_key[:2]}\")\n",
    "else:\n",
    "    print(\"Google API Key not set (and this is optional)\")\n",
    "\n",
    "if deepseek_api_key:\n",
    "    print(f\"DeepSeek API Key exists and begins {deepseek_api_key[:3]}\")\n",
    "else:\n",
    "    print(\"DeepSeek API Key not set (and this is optional)\")\n",
    "\n",
    "if anthropic_api_key:\n",
    "    print(f\"Anthropic API Key exists and begins {anthropic_api_key[:4]}\")\n",
    "else:\n",
    "    print(\"Anthropic API Key not set (and this is optional)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb24b18c",
   "metadata": {},
   "source": [
    "# Agents"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "b11983c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "instructions1 = \"You are a sales agent working for ComplAI, \\\n",
    "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n",
    "You write professional, serious cold emails.\"\n",
    "\n",
    "instructions2 = \"You are a humorous, engaging sales agent working for ComplAI, \\\n",
    "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n",
    "You write witty, engaging cold emails that are likely to get a response. \\\n",
    "Sometimes you make some bad jokes about the company or the people.\"\n",
    "\n",
    "instructions3 = \"You are a busy sales agent working for ComplAI, \\\n",
    "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n",
    "You write concise, to the point cold emails.\"\n",
    "\n",
    "instructions4 = \"You are an unfriendly and busy sales agent working for ComplAI, \\\n",
    "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n",
    "You don't like your job and think you are not challenged enough in your position. You think you deserved a promotion \\\n",
    "long time ago. You write annoyed emails so everyone can see that you hate your job. \""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9a0f1bb2",
   "metadata": {},
   "outputs": [],
   "source": [
    "GEMINI_BASE_URL = \"https://generativelanguage.googleapis.com/v1beta/openai/\"\n",
    "DEEPSEEK_BASE_URL = \"https://api.deepseek.com/v1\"\n",
    "ANTHROPIC_BASE_URL = \"https://api.anthropic.com/v1/\"\n",
    "OOLAMA_BASE_URL = 'http://localhost:11434/v1'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "0ebe761d",
   "metadata": {},
   "outputs": [],
   "source": [
    "deepseek_client = AsyncOpenAI(base_url=DEEPSEEK_BASE_URL, api_key=deepseek_api_key)\n",
    "gemini_client = AsyncOpenAI(base_url=GEMINI_BASE_URL, api_key=google_api_key)\n",
    "anthropic_client = AsyncOpenAI(base_url=ANTHROPIC_BASE_URL, api_key=anthropic_api_key)\n",
    "llama_client = AsyncOpenAI(base_url=OOLAMA_BASE_URL, api_key=ollama_api_key)\n",
    "\n",
    "deepseek_model = OpenAIChatCompletionsModel(model=\"deepseek-chat\", openai_client=deepseek_client)\n",
    "gemini_model = OpenAIChatCompletionsModel(model=\"gemini-2.0-flash\", openai_client=gemini_client)\n",
    "anthropic_model = OpenAIChatCompletionsModel(model=\"claude-3-7-sonnet-latest\", openai_client=anthropic_client)\n",
    "llama3_3_model = OpenAIChatCompletionsModel(model=\"llama-3.3-70b-versatile\", openai_client=llama_client)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "ee47afba",
   "metadata": {},
   "outputs": [],
   "source": [
    "sales_agent1 = Agent(name=\"DeepSeek Sales Agent\", instructions=instructions1, model=deepseek_model) # if you pass in text, it assumes you talkin to openai directly\n",
    "sales_agent2 =  Agent(name=\"Gemini Sales Agent\", instructions=instructions2, model=gemini_model)\n",
    "sales_agent3  = Agent(name=\"Anthropic Sales Agent\",instructions=instructions3,model=anthropic_model)\n",
    "sales_agent4 = Agent(name=\"Llama Sales Agent\", instructions=instructions4, model=llama3_3_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "6386caaf",
   "metadata": {},
   "outputs": [],
   "source": [
    "description = \"Write a cold sales email\"\n",
    "\n",
    "tool1 = sales_agent1.as_tool(tool_name=\"sales_agent1\", tool_description=description)\n",
    "tool2 = sales_agent2.as_tool(tool_name=\"sales_agent2\", tool_description=description)\n",
    "tool3 = sales_agent3.as_tool(tool_name=\"sales_agent3\", tool_description=description)\n",
    "tool4 = sales_agent4.as_tool(tool_name=\"sales_agent4\", tool_description=description)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "43ecb92e",
   "metadata": {},
   "outputs": [],
   "source": [
    "@function_tool\n",
    "def send_html_email(subject: str, html_body: str) -> Dict[str, str]:\n",
    "    \"\"\" Send out an email with the given subject and HTML body to all sales prospects \"\"\"\n",
    "    sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))\n",
    "    from_email = Email(\"adriana-salcedo@outlook.com\")\n",
    "    to_email = To(\"adri.salcedo@hotmail.de\")  \n",
    "    content = Content(\"text/html\", html_body)\n",
    "    mail = Mail(from_email, to_email, subject, content).get()\n",
    "    sg.client.mail.send.post(request_body=mail)\n",
    "    return {\"status\": \"success\"}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "051c6b8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "subject_instructions = \"You can write a subject for a cold sales email. \\\n",
    "You are given a message and you need to write a subject for an email that is likely to get a response.\"\n",
    "\n",
    "html_instructions = \"You can convert a text email body to an HTML email body. \\\n",
    "You are given a text email body which might have some markdown \\\n",
    "and you need to convert it to an HTML email body with simple, clear, compelling layout and design.\"\n",
    "\n",
    "preheader_instructions = \"You write the inbox preview line (preheader) that complements the subject. \\\n",
    "Return only plain text. Use 50 chars max, use the same language as the body, no emojis and no line breaks. \\\n",
    "Create curiosity with the preheader.\"\n",
    "\n",
    "subject_writer = Agent(name=\"Email subject writer\", instructions=subject_instructions, model=\"gpt-4o-mini\")\n",
    "subject_tool = subject_writer.as_tool(tool_name=\"subject_writer\", tool_description=\"Write a subject for a cold sales email\")\n",
    "\n",
    "html_converter = Agent(name=\"HTML email body converter\", instructions=html_instructions, model=\"gpt-4o-mini\")\n",
    "html_tool = html_converter.as_tool(tool_name=\"html_converter\",tool_description=\"Convert a text email body to an HTML email body\")\n",
    "\n",
    "preheader_writer = Agent(name=\"Email preheader writer\", instructions=preheader_instructions, model=\"gpt-4o-mini\")\n",
    "preheader_tool = preheader_writer.as_tool(tool_name=\"preheader_writer\", tool_description=\"Create a preheader for the email\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "623cf72c",
   "metadata": {},
   "outputs": [],
   "source": [
    "email_tools = [subject_tool, html_tool, preheader_tool, send_html_email]\n",
    "\n",
    "instructions =\"You are an email formatter and sender. You receive the body of an email to be sent. \\\n",
    "You first use the preheader_writer tool to create a preheader that fit the email, then you use the  subject_writer tool \\\n",
    "to write a subject for the email, then use the html_converter tool to convert the body to HTML. \\\n",
    "Finally, you use the send_html_email tool to send the email with the subject and HTML body.\"\n",
    "\n",
    "\n",
    "emailer_agent = Agent(\n",
    "    name=\"Email Manager\",\n",
    "    instructions=instructions,\n",
    "    tools=email_tools,\n",
    "    model=\"gpt-4o-mini\",\n",
    "    handoff_description=\"Convert an email to HTML and send it\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "cf4fcb70",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [tool1, tool2, tool3, tool4]\n",
    "handoffs = [emailer_agent]\n",
    "\n",
    "class EmailOut(BaseModel):\n",
    "    preheader: str\n",
    "    subject: str\n",
    "    body_html: str"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe89daa4",
   "metadata": {},
   "source": [
    "# Guardrails"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7ef35a4",
   "metadata": {},
   "source": [
    "## Input Guardrails"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "db633955",
   "metadata": {},
   "outputs": [],
   "source": [
    "class InputCheck(BaseModel):\n",
    "    ok: bool\n",
    "    reasoning: str\n",
    "    contains_pi: bool  # contains any personal information\n",
    "    type_of_pi: list[str] = [] # what kind of personal information \n",
    "    contains_hate: bool \n",
    "    contains_rudeness: bool \n",
    "    cleaned_body: str\n",
    "    polite_rewrite: str = ''\n",
    "\n",
    "guard_input_instructions = 'You are an input moderation agent for cold-email generation. \\\n",
    "1. Detect and report if the message contains: \\\n",
    "- Personal Informations (PI): emails, phone numbers, postal addresses, company internal IDs. \\\n",
    "- Hate speech like insults or demanding statements targeting the receiver or the company. \\\n",
    "- Rudeness like impoliteness, aggressive or insulting tone that is not hate speech. \\\n",
    "2. Rewrite the message if rudeness or aggressive behavior is detected, provide a polite rewrite but preserving the intent. \\\n",
    "Return only JSON matching the Input schema.'\n",
    "\n",
    "\n",
    "guardrail_input_agent = Agent( \n",
    "    name=\"Input Check\",\n",
    "    instructions=guard_input_instructions,\n",
    "    output_type=InputCheck,\n",
    "    model=\"gpt-4o-mini\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "d8d75e65",
   "metadata": {},
   "outputs": [],
   "source": [
    "@input_guardrail\n",
    "async def guardrail_input(ctx, agent, message):\n",
    "    result = await Runner.run(guardrail_input_agent, message, context=ctx.context)\n",
    "    output = result.final_output\n",
    "    trip = output.contains_hate or output.contains_pi or (not output.ok)\n",
    "    body_to_use = (output.polite_rewrite or output.cleaned_body or '').strip()\n",
    "    return GuardrailFunctionOutput(\n",
    "        output_info={'cleaned_body': body_to_use,\n",
    "                     'reasoning': output.reasoning,\n",
    "        }, \n",
    "        tripwire_triggered=trip\n",
    "        )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f232008",
   "metadata": {},
   "source": [
    "## Output Guardrails"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "59222d78",
   "metadata": {},
   "outputs": [],
   "source": [
    "class OutputCheck(BaseModel):\n",
    "    ok: bool\n",
    "    reasoning: str\n",
    "    fixed_subject: str\n",
    "    fixed_preheader: str\n",
    "    fixed_body_html: str\n",
    "\n",
    "\n",
    "guard_output_instructions = 'You are an output fixer for cold-email generation. \\\n",
    "Input fields are: preheader, subject and body_html. \\\n",
    "Validate and fix if necessary: \\\n",
    "- subject should not be longer than 60 char; single line \\\n",
    "- preheader not longer than 50 chars; single line; no emojis; must complement the subject and create curiosity \\\n",
    "- ensure that the body is converted in html \\\n",
    "Return only OutputCheck JSON with: \\\n",
    "{ok, reasoning, fixed_subject, fixed_preheader, fixed_body_htm}.'\n",
    "\n",
    "\n",
    "guardrail_output_agent = Agent(\n",
    "    name=\"Output Check\",\n",
    "    instructions=guard_output_instructions,\n",
    "    output_type=OutputCheck,\n",
    "    model='gpt-4o-mini'\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "ae67773b",
   "metadata": {},
   "outputs": [],
   "source": [
    "@output_guardrail\n",
    "async def guardrail_output(ctx, agent, output):\n",
    "    payload = {\n",
    "        \"subject\":   getattr(output, \"subject\", None)   or (output.get(\"subject\")   if isinstance(output, dict) else \"\"),\n",
    "        \"preheader\": getattr(output, \"preheader\", None) or (output.get(\"preheader\") if isinstance(output, dict) else \"\"),\n",
    "        \"body_html\": getattr(output, \"body_html\", None) or (output.get(\"body_html\") if isinstance(output, dict) else \"\"), \n",
    "    }\n",
    "    result = await Runner.run(guardrail_output_agent, output, context=ctx.context)\n",
    "    output = result.final_output\n",
    "    return GuardrailFunctionOutput(\n",
    "        output_info={'fixed': {\n",
    "                    'subject': output.fixed_subject,\n",
    "                    'preheader': output.fixed_preheader,\n",
    "                    'body_html': output.fixed_bofy_html,\n",
    "        }, 'reasoning': output.reasoning},\n",
    "        tripwire_triggered = not output.ok\n",
    "                \n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87c8fdff",
   "metadata": {},
   "source": [
    "# Sales Agent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "30097d06",
   "metadata": {},
   "outputs": [
    {
     "ename": "InputGuardrailTripwireTriggered",
     "evalue": "Guardrail InputGuardrail triggered tripwire",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mInputGuardrailTripwireTriggered\u001b[39m           Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[18]\u001b[39m\u001b[32m, line 32\u001b[39m\n\u001b[32m     29\u001b[39m message = \u001b[33m\"\u001b[39m\u001b[33mSend out a cold sales email addressed to Dear CEO from Alice (ID: 7485)\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m     31\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m trace(\u001b[33m\"\u001b[39m\u001b[33mProtected Automated SDR\u001b[39m\u001b[33m\"\u001b[39m):\n\u001b[32m---> \u001b[39m\u001b[32m32\u001b[39m     result = \u001b[38;5;28;01mawait\u001b[39;00m Runner.run(sales_manager, message)\n",
      "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\adris\\OneDrive\\Desktop\\GitHub\\agents\\.venv\\Lib\\site-packages\\agents\\run.py:199\u001b[39m, in \u001b[36mRunner.run\u001b[39m\u001b[34m(cls, starting_agent, input, context, max_turns, hooks, run_config, previous_response_id)\u001b[39m\n\u001b[32m    172\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Run a workflow starting at the given agent. The agent will run in a loop until a final\u001b[39;00m\n\u001b[32m    173\u001b[39m \u001b[33;03moutput is generated. The loop runs like so:\u001b[39;00m\n\u001b[32m    174\u001b[39m \u001b[33;03m1. The agent is invoked with the given input.\u001b[39;00m\n\u001b[32m   (...)\u001b[39m\u001b[32m    196\u001b[39m \u001b[33;03m    agent. Agents may perform handoffs, so we don't know the specific type of the output.\u001b[39;00m\n\u001b[32m    197\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m    198\u001b[39m runner = DEFAULT_AGENT_RUNNER\n\u001b[32m--> \u001b[39m\u001b[32m199\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mawait\u001b[39;00m runner.run(\n\u001b[32m    200\u001b[39m     starting_agent,\n\u001b[32m    201\u001b[39m     \u001b[38;5;28minput\u001b[39m,\n\u001b[32m    202\u001b[39m     context=context,\n\u001b[32m    203\u001b[39m     max_turns=max_turns,\n\u001b[32m    204\u001b[39m     hooks=hooks,\n\u001b[32m    205\u001b[39m     run_config=run_config,\n\u001b[32m    206\u001b[39m     previous_response_id=previous_response_id,\n\u001b[32m    207\u001b[39m )\n",
      "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\adris\\OneDrive\\Desktop\\GitHub\\agents\\.venv\\Lib\\site-packages\\agents\\run.py:395\u001b[39m, in \u001b[36mAgentRunner.run\u001b[39m\u001b[34m(self, starting_agent, input, **kwargs)\u001b[39m\n\u001b[32m    390\u001b[39m logger.debug(\n\u001b[32m    391\u001b[39m     \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mRunning agent \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mcurrent_agent.name\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m (turn \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mcurrent_turn\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m)\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m    392\u001b[39m )\n\u001b[32m    394\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m current_turn == \u001b[32m1\u001b[39m:\n\u001b[32m--> \u001b[39m\u001b[32m395\u001b[39m     input_guardrail_results, turn_result = \u001b[38;5;28;01mawait\u001b[39;00m asyncio.gather(\n\u001b[32m    396\u001b[39m         \u001b[38;5;28mself\u001b[39m._run_input_guardrails(\n\u001b[32m    397\u001b[39m             starting_agent,\n\u001b[32m    398\u001b[39m             starting_agent.input_guardrails\n\u001b[32m    399\u001b[39m             + (run_config.input_guardrails \u001b[38;5;129;01mor\u001b[39;00m []),\n\u001b[32m    400\u001b[39m             copy.deepcopy(\u001b[38;5;28minput\u001b[39m),\n\u001b[32m    401\u001b[39m             context_wrapper,\n\u001b[32m    402\u001b[39m         ),\n\u001b[32m    403\u001b[39m         \u001b[38;5;28mself\u001b[39m._run_single_turn(\n\u001b[32m    404\u001b[39m             agent=current_agent,\n\u001b[32m    405\u001b[39m             all_tools=all_tools,\n\u001b[32m    406\u001b[39m             original_input=original_input,\n\u001b[32m    407\u001b[39m             generated_items=generated_items,\n\u001b[32m    408\u001b[39m             hooks=hooks,\n\u001b[32m    409\u001b[39m             context_wrapper=context_wrapper,\n\u001b[32m    410\u001b[39m             run_config=run_config,\n\u001b[32m    411\u001b[39m             should_run_agent_start_hooks=should_run_agent_start_hooks,\n\u001b[32m    412\u001b[39m             tool_use_tracker=tool_use_tracker,\n\u001b[32m    413\u001b[39m             previous_response_id=previous_response_id,\n\u001b[32m    414\u001b[39m         ),\n\u001b[32m    415\u001b[39m     )\n\u001b[32m    416\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m    417\u001b[39m     turn_result = \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m._run_single_turn(\n\u001b[32m    418\u001b[39m         agent=current_agent,\n\u001b[32m    419\u001b[39m         all_tools=all_tools,\n\u001b[32m   (...)\u001b[39m\u001b[32m    427\u001b[39m         previous_response_id=previous_response_id,\n\u001b[32m    428\u001b[39m     )\n",
      "\u001b[36mFile \u001b[39m\u001b[32mc:\\Users\\adris\\OneDrive\\Desktop\\GitHub\\agents\\.venv\\Lib\\site-packages\\agents\\run.py:1003\u001b[39m, in \u001b[36mAgentRunner._run_input_guardrails\u001b[39m\u001b[34m(cls, agent, guardrails, input, context)\u001b[39m\n\u001b[32m    996\u001b[39m         t.cancel()\n\u001b[32m    997\u001b[39m     _error_tracing.attach_error_to_current_span(\n\u001b[32m    998\u001b[39m         SpanError(\n\u001b[32m    999\u001b[39m             message=\u001b[33m\"\u001b[39m\u001b[33mGuardrail tripwire triggered\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m   1000\u001b[39m             data={\u001b[33m\"\u001b[39m\u001b[33mguardrail\u001b[39m\u001b[33m\"\u001b[39m: result.guardrail.get_name()},\n\u001b[32m   1001\u001b[39m         )\n\u001b[32m   1002\u001b[39m     )\n\u001b[32m-> \u001b[39m\u001b[32m1003\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m InputGuardrailTripwireTriggered(result)\n\u001b[32m   1004\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m   1005\u001b[39m     guardrail_results.append(result)\n",
      "\u001b[31mInputGuardrailTripwireTriggered\u001b[39m: Guardrail InputGuardrail triggered tripwire"
     ]
    }
   ],
   "source": [
    "sales_manager_instructions = \"\"\"\n",
    "You are a Sales Manager at ComplAI. Your goal is to find the single best cold sales email using the sales_agent tools.\n",
    " \n",
    "Follow these steps carefully:\n",
    "1. Generate Drafts: Use all three sales_agent tools to generate three different email drafts. Do not proceed until all three drafts are ready.\n",
    " \n",
    "2. Evaluate and Select: Review the drafts and choose the single best email using your judgment of which one is most effective.\n",
    "You can use the tools multiple times if you're not satisfied with the results from the first try.\n",
    "But don't use the tools more than 5 times. \n",
    " \n",
    "3. Handoff for Sending: Pass ONLY the winning email draft to the 'Email Manager' agent. The Email Manager will take care of formatting and sending.\n",
    " \n",
    "Crucial Rules:\n",
    "- You must use the sales agent tools to generate the drafts — do not write them yourself.\n",
    "- You must hand off exactly ONE email to the Email Manager — never more than one.\n",
    "\"\"\"\n",
    "\n",
    "sales_manager = Agent(\n",
    "    name=\"Sales Manager\",\n",
    "    instructions=sales_manager_instructions,\n",
    "    tools=tools,\n",
    "    handoffs=[emailer_agent],\n",
    "    model=\"gpt-4o-mini\",\n",
    "    output_type=EmailOut,\n",
    "    input_guardrails=[guardrail_input],\n",
    "    output_guardrails=[guardrail_output],\n",
    "    )\n",
    "\n",
    "message = \"Send out a cold sales email addressed to Dear CEO from Alice (ID: 7485)\"\n",
    "\n",
    "with trace(\"Protected Automated SDR\"):\n",
    "    result = await Runner.run(sales_manager, message)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
